package org.bing.zion.filter.serializer;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Simple object deserializer
 * 
 * @author chenbin
 * 
 */

public class SimpleReflectDecoder extends SimpleTypeCodec {

	public Object read(String s) throws IOException {
		Map<Integer, Object> map = new HashMap<Integer, Object>();
		try {
			return this.read(null, s, map);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	private Object read(Object owner, String s, Map<Integer, Object> map) throws NegativeArraySizeException,
			ClassNotFoundException {
		int hb = 0;
		int he = s.indexOf(">") + 1;
		String head = s.substring(hb, he);
		String type = this.readClazzName(head);
		if (head.startsWith("<l")) { // list
			int len = readLength(head);
			int eb = he;
			int ee = s.length();
			List list = new ArrayList();
			for (int i = 0; i < len; i++) {
				Element el = this.readElement(s, eb, ee);
				String es = s.substring(el.bInx, el.eInx);
				eb = el.eInx;
				Object obj = this.read(null, es, map);
				list.add(obj);
			}
			if (owner == null) {
				return list;
			} else {
				String field = this.readFieldName(s);
				ObjectReflectHelper helper = ObjectReflectHelper.instance(owner.getClass());
				helper.setValue(owner, field, list);
			}
			return list;
		} else if (type == null) { // ref, not list
			int id = this.readObjectRef(s);
			if (id > 0) {
				Object ref = map.get(id);
				if (owner == null) {
					return ref;
				} else {
					String field = this.readFieldName(s);
					ObjectReflectHelper helper = ObjectReflectHelper.instance(owner.getClass());
					helper.setValue(owner, field, ref);
				}
			} else {
				if (head.indexOf("<null />") >= 0) { // null
					return null;
				}
			}
		} else if (isSimpleType(type)) { // simple type
			Object obj = this.simpleDecode(s);
			if (owner == null) {
				return obj;
			} else {
				String field = this.readFieldName(s);
				ObjectReflectHelper helper = ObjectReflectHelper.instance(owner.getClass());
				helper.setValue(owner, field, obj);
			}
		} else if (head.startsWith("<a")) { // array
			int len = readLength(head);
			int eb = he;
			int ee = s.length();
			Object array = Array.newInstance(Class.forName(type), len);
			for (int i = 0; i < len; i++) {
				Element el = this.readElement(s, eb, ee);
				String es = s.substring(el.bInx, el.eInx);
				eb = el.eInx;
				Object obj = this.read(null, es, map);
				Array.set(array, i, obj);
			}
			if (owner == null) {
				return array;
			} else {
				String field = this.readFieldName(s);
				ObjectReflectHelper helper = ObjectReflectHelper.instance(owner.getClass());
				helper.setValue(owner, field, array);
			}
			return array;
		} else { // complex type
			int eb = he;
			int ee = s.length();
			Object obj = newInstance(type);
			// set id and obj map
			int id = this.readObjectId(s);
			map.put(id, obj);
			while (true) {
				Element el = readElement(s, eb, ee);
				if (el.bInx < 0 || el.eInx < 0) {
					break;
				}
				eb = el.eInx;
				String es = s.substring(el.bInx, el.eInx);
				this.read(obj, es, map);
			}
			if (owner == null) {
				return obj;
			} else {
				String field = this.readFieldName(s);
				ObjectReflectHelper helper = ObjectReflectHelper.instance(owner.getClass());
				helper.setValue(owner, field, obj);
			}
			return obj;
		}

		return null;
	}

	private Element readElement(String s, int beginInx, int endInx) {
		Element el = new Element(-1, -1);
		int num = 0;
		boolean f = false;
		for (int i = beginInx; i < endInx; i++) {
			char c = s.charAt(i);
			if (c == '<') { // begin '<...', or end </...>
				if ('/' == s.charAt(i + 1)) {
					if (num == 0) {
						int pos = s.indexOf('>', i + 2);
						if (pos > 0) {
							el.eInx = pos + 1;
						}
						break;
					} else {
						num--;
					}
				} else if (!f) {
					el.bInx = i;
					f = true;
				} else {
					num++;
				}
			} else if (c == '/') { // end '/>'
				if ('>' == s.charAt(i + 1)) {
					if (num == 0) {
						el.eInx = i + 2;
						break;
					} else {
						num--;
					}
				}
			}
		}
		return el;
	}

	/** inner helper clazz */
	class Element {
		int bInx;
		int eInx;

		Element(int bInx, int eInx) {
			this.bInx = bInx;
			this.eInx = eInx;
		}
	}
}
